package com.ksyun.alert.service.impl;

import com.ksyun.alert.biz.bo.timing.TimingExpressionResolveBO;
import com.ksyun.alert.biz.bo.timing.TimingSectionCalculateParamsBO;
import com.ksyun.alert.customException.SkyAlertException;
import com.ksyun.alert.entity.AlertMetricsDO;
import com.ksyun.alert.entity.AlertRecordsDO;
import com.ksyun.alert.entity.AlertRulesDO;
import com.ksyun.alert.entity.MonitorDatasDO;
import com.ksyun.alert.service.AlertMetricsService;
import com.ksyun.alert.service.AlertRecordsService;
import com.ksyun.alert.service.CalculateTimingService;
import com.ksyun.alert.service.MonitorDatasService;
import com.ksyun.alert.utils.AggregateOperations.Params.RateParam;
import com.ksyun.alert.utils.QLExpressUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.ksyun.alert.utils.QLExpressUtil.doCalculate;

/**
 * 定时计算业务层接口实现层
 * @author hantou
 * @date 2024-05-11
 */
@Service
public class CalculateTimingServiceImpl implements CalculateTimingService {

    @Resource
    AlertMetricsService alertMetricsService;

    @Resource
    private AlertRecordsService alertRecordsService;

    @Resource
    private MonitorDatasService monitorDatasService;

    @Transactional
    public void doTransientCalculate(long nowTimestamp, AlertRulesDO alertRulesDO) {
        List<String> neededMetricsList = Arrays.stream(alertRulesDO.getMetric().split(","))
                .collect(Collectors.toList());
        List<MonitorDatasDO> monitorsList = monitorDatasService.getSpecifiedTimeNewestMonitorData(neededMetricsList, nowTimestamp);
        if(CollectionUtils.isEmpty(monitorsList)){
            return;
        }
        Map<String, Float> paramsMap = new HashMap<>();
        for (MonitorDatasDO monitorData : monitorsList) {
            paramsMap.putIfAbsent(monitorData.getMetricName(), monitorData.getValue());
        }

        Long timestamp = monitorsList.get(0).getTimestamp();
        String hostName = monitorsList.get(0).getHostName();

        try {
            Object result = QLExpressUtil.doCalculate(alertRulesDO.getExpression(), paramsMap);
            if(QLExpressUtil.doCalComparison(result, alertRulesDO.getComparison(), alertRulesDO.getThreshold())){
                AlertRecordsDO alertRecordsDO = AlertRecordsDO.builder()
                        .alertMessage(alertRulesDO.getDescription())
                        .timestamp(timestamp)
                        .ruleId(alertRulesDO.getId())
                        .severity(alertRulesDO.getSeverity())
                        .hostName(hostName)
                        .build();
                alertRecordsService.getBaseMapper().insert(alertRecordsDO);

                // 把每个指标存一份
                for (String metric : neededMetricsList) {
                    AlertMetricsDO alertMetricsDO = AlertMetricsDO.builder()
                            .value(paramsMap.get(metric))
                            .recordId(alertRecordsDO.getId())
                            .metricName(metric)
                            .build();
                    alertMetricsService.getBaseMapper().insert(alertMetricsDO);
                }
                // 把算式结果也存一份
                AlertMetricsDO expressionResult = AlertMetricsDO.builder()
                        .value((float)result)
                        .recordId(alertRecordsDO.getId())
                        .metricName("expressionValue")
                        .build();
                alertMetricsService.getBaseMapper().insert(expressionResult);
            }
        }catch (Exception e){
            throw new SkyAlertException(e.getMessage());
        }
    }

    @Transactional
    public void doSectionCalculate(long nowTimestamp, AlertRulesDO alertRulesDO) {
        List<String> neededMetricsList = Arrays.stream(alertRulesDO.getMetric().split(","))
                .collect(Collectors.toList());

        TimingExpressionResolveBO timingExpressionResolveBO = QLExpressUtil.resolveExpression(alertRulesDO.getExpression(), neededMetricsList);
        String originalExpression = alertRulesDO.getExpression();
        // 将解析完参数后的表达式替换原规则中的表达式
        alertRulesDO.setExpression(timingExpressionResolveBO.getExpression());
        List<List<MonitorDatasDO>> givenMetricsLists = monitorDatasService.getSpecifiedTimePeriodMonitorData(timingExpressionResolveBO, nowTimestamp);

        if(CollectionUtils.isEmpty(givenMetricsLists) || givenMetricsLists.size() < 2){
            return;
        }

        TimingSectionCalculateParamsBO timingSectionCalculateParamsBO = resolve2SectionParams(givenMetricsLists, alertRulesDO);
        try {
            // 对Rate的参数特殊处理
            Object result = doCalculate(alertRulesDO.getExpression(), timingSectionCalculateParamsBO.getParamsListsMap());
            if(QLExpressUtil.doCalComparison(result, alertRulesDO.getComparison(), alertRulesDO.getThreshold())){
                AlertRecordsDO alertRecordsDO = AlertRecordsDO.builder()
                        .alertMessage(alertRulesDO.getDescription())
                        .timestamp(timingSectionCalculateParamsBO.getTimestamp())
                        .ruleId(alertRulesDO.getId())
                        .severity(alertRulesDO.getSeverity())
                        .hostName(timingSectionCalculateParamsBO.getHostName())
                        .build();
                alertRecordsService.getBaseMapper().insert(alertRecordsDO);

                // 对于定时区间数据计算，告警数据表只保存公式计算的结果
                AlertMetricsDO alertMetricsDO = AlertMetricsDO.builder()
                        .value((Float) result)
                        .recordId(alertRecordsDO.getId())
//                        .metricName(originalExpression)
                        .metricName("expressionValue")
                        .build();
                alertMetricsService.getBaseMapper().insert(alertMetricsDO);
            }
        }catch (Exception e){
           throw new SkyAlertException(e.getMessage());
        }
    }

    /**
     * 解析区间数据参数
     * @param givenMetricLists 参数列表集
     * @param alertRulesDO 告警规则
     */
    private TimingSectionCalculateParamsBO resolve2SectionParams(List<List<MonitorDatasDO>> givenMetricLists, AlertRulesDO alertRulesDO) {
        List<String> neededMetricsList = Arrays.stream(alertRulesDO.getMetric().split(","))
                .collect(Collectors.toList());

        // 对Rate包裹的参数特殊处理
        String expression = alertRulesDO.getExpression();
        List<String> rateWrappedParamsList = new ArrayList<>();
        Matcher matcher = Pattern.compile("rate\\((.*?)\\)").matcher(expression);
        while (matcher.find()) {
            String rateWrappedParam = matcher.group(1);
            rateWrappedParamsList.add(rateWrappedParam);
            neededMetricsList.remove(rateWrappedParam);
        }

        Collections.sort(givenMetricLists, Comparator.comparingLong(list -> list.get(0).getTimestamp()));
        Map<String, List<Object>> paramsMap = new HashMap<>();
        for (List<MonitorDatasDO> givenMetricList : givenMetricLists) {
            // 按时间戳从大到小进行排序
            for (MonitorDatasDO metricDO : givenMetricList) {
                if(neededMetricsList.contains(metricDO.getMetricName())){
                    paramsMap.putIfAbsent(metricDO.getMetricName(), new ArrayList<>());
                    paramsMap.get(metricDO.getMetricName()).add(metricDO.getValue());
                }else if(rateWrappedParamsList.contains(metricDO.getMetricName())){ // 特殊处理rate的参数
                    paramsMap.putIfAbsent(metricDO.getMetricName(), new ArrayList<>());
                    paramsMap.get(metricDO.getMetricName()).add(RateParam.builder()
                            .value(metricDO.getValue())
                            .timestamp(metricDO.getTimestamp())
                            .build());
                }
            }
        }

        return TimingSectionCalculateParamsBO.builder()
                .paramsListsMap(paramsMap)
                .hostName(givenMetricLists.get(0).get(0).getHostName())
                .timestamp(givenMetricLists.get(0).get(0).getTimestamp()) // 使用数据中的时间戳
                .build();
    }

}
